{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Breaking Hardware ECC on CW305 FPGA, part 3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This builds on CW305_ECC.ipynb and CW305_ECC_part2.ipynb; be sure to digest them before starting this one.\n",
    "\n",
    "In this notebook, we study how small modifications to the target Verilog source code which may be able to reduce the side-channel leakage.\n",
    "\n",
    "We'll try three different approaches; for each, we'll evaluate the countermeasure efficacy by running the attacks developed in the previous notebook in this series.\n",
    "\n",
    "Each approach uses a different target bitfile. The bitfiles for the CW305_100t target are included in ChipWhisperer; bitfiles for the other targets are forthcoming. Until then you'll have to generate them yourself using Vivado ([project files](https://github.com/newaetech/chipwhisperer/tree/develop/firmware/fpgas/ecc/vivado)).\n",
    "\n",
    "The tutorial was developed with a CW-Pro with the CW305 100t target FPGA; the observations made in the attack's development should be accurate if you're using the same, but other combinations of CW-Pro / CW-Lite / CW-Husky / CW305 100t / 35t / CW312T-A35 may behave somewhat differently (some definitely do!)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup\n",
    "\n",
    "See CW305_ECC_part1.ipynb for explanations which are not repeated here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#PLATFORM = 'CWLITE'\n",
    "#PLATFORM = 'CWPRO'\n",
    "PLATFORM = 'CWHUSKY'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "TARGET_PLATFORM = 'CW305_100t'\n",
    "#TARGET_PLATFORM = 'CW305_35t'\n",
    "#TARGET_PLATFORM = 'CW312T_A35'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "TRACES = 'HARDWARE' # if you have the required capture+target hardware: capture actual traces\n",
    "#TRACES = 'SIMULATED' # if you don't have capture+target hardware: use pre-captured traces (these traces were obtained using CW-Husky with a  CW305_100t)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import chipwhisperer as cw\n",
    "\n",
    "if TRACES != 'SIMULATED':\n",
    "    scope = cw.scope()\n",
    "    scope.default_setup()\n",
    "    if TARGET_PLATFORM == 'CW312T_A35':\n",
    "        scope.io.hs2 = 'clkgen'\n",
    "        fpga_id = 'cw312t_a35'\n",
    "        platform = 'ss2'\n",
    "    else:\n",
    "        scope.io.hs2 = \"disabled\"\n",
    "        platform = 'cw305'\n",
    "        if TARGET_PLATFORM == 'CW305_100t':\n",
    "            fpga_id = '100t'\n",
    "        elif TARGET_PLATFORM == 'CW305_35t':\n",
    "            fpga_id = '35t'\n",
    "\n",
    "    target = cw.target(scope, cw.targets.CW305_ECC, force=True, fpga_id=fpga_id, platform=platform)\n",
    "    \n",
    "    # ensure ADC is locked:\n",
    "    scope.clock.reset_adc()\n",
    "    assert (scope.clock.adc_locked), \"ADC failed to lock\"\n",
    "\n",
    "%run \"CW305_ECC_setup.ipynb\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Attempt #1\n",
    "\n",
    "The first countermeasure attempt is a naive approach at masking the leakage caused by `move_inhibit` (see part 1 for discussion of this). Briefly: we duplicate the BRAMs which are the destination memories for the `move_inhibit`-controlled memory writes, so that when `move_inhibit` is set, we write the result to the new memories (instead of blocking the write to the original memories).\n",
    "\n",
    "The new memories are never read from in normal operation; they are dummy memories, there for the sole purpose of carrying out a memory write, to make the power signature \"look\" the same indepedent of `move_inhibit`.\n",
    "\n",
    "We take some care to ensure that the original and dummy memories are as alike as possible, but we don't take any precautions for their relative placement on the FPGA.\n",
    "\n",
    "Each countermeasure attempt has its own FPGA bitfile; we begin by loading the appropriate bitfile:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "change_bitfile('attempt1')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k = 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000\n",
    "traces = get_traces(1, k, 'part3_1', full=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's begin by looking at the raw difference between ones and zeros, as we did in part 1:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "samples = 4204\n",
    "trace = traces[0]\n",
    "avg_ones = np.zeros(samples)\n",
    "for start in cycles[1:128]:\n",
    "    avg_ones += trace.wave[start:start+samples]\n",
    "avg_ones /= 128\n",
    "\n",
    "avg_zeros = np.zeros(samples)\n",
    "for start in cycles[128:256]:\n",
    "    avg_zeros += trace.wave[start:start+samples]\n",
    "avg_zeros /= 128"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.plotting import figure, show\n",
    "from bokeh.resources import INLINE\n",
    "from bokeh.io import push_notebook, output_notebook\n",
    "from ipywidgets import interact, Layout\n",
    "\n",
    "output_notebook(INLINE)\n",
    "s = figure(width=2000)\n",
    "\n",
    "xrange = list(range(len(avg_ones)))\n",
    "s.line(xrange, avg_ones - avg_zeros, line_color=\"orange\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The leakage is still present! Let's quickly compare it to the leakage from the original target bitfile:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "change_bitfile('original')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "otraces = get_traces(1, k, 'part1_1', full=True)\n",
    "otrace = otraces[0]\n",
    "\n",
    "oavg_ones = np.zeros(samples)\n",
    "for start in cycles[1:128]:\n",
    "    oavg_ones += otrace.wave[start:start+samples]\n",
    "oavg_ones /= 128\n",
    "\n",
    "oavg_zeros = np.zeros(samples)\n",
    "for start in cycles[128:256]:\n",
    "    oavg_zeros += otrace.wave[start:start+samples]\n",
    "oavg_zeros /= 128"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.models import Legend, LegendItem\n",
    "\n",
    "diff = figure(width=2000)\n",
    "\n",
    "odiff = oavg_ones - oavg_zeros\n",
    "newdiff = avg_ones - avg_zeros\n",
    "\n",
    "compressed_odiff = np.append(odiff[0:15], odiff[4195:])\n",
    "compressed_newdiff = np.append(newdiff[0:15], newdiff[4195:])\n",
    "xrange = list(range(len(compressed_newdiff)))\n",
    "O = diff.line(xrange, compressed_odiff, line_color=\"black\")\n",
    "N = diff.line(xrange, compressed_newdiff, line_color=\"orange\", line_width=2)\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='original 0/1 difference', renderers=[O]),\n",
    "    LegendItem(label='new 0/1 difference', renderers=[N]),\n",
    "])\n",
    "diff.add_layout(legend)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(diff)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Not only is the leakage still present -- we've actually increased it! \n",
    "\n",
    "The leakage at cycles 6-7 is easiest to understand. First, a bit more background which wasn't explicitely discussed in part 1, but which you may have guessed at: from the simulation, we can see that during cycles 6-7, the core is reading back the data which was written during cycles 4202-4203. So when `move_inhibit` is **not** set, the core writes data and then reads the same data back, and when it **is** set, the core does not write data and then reads **different** data back. So if the leakage is due to reading the same data which was just written, then our countermeasure hasn't changed anything to prevent this leakage.\n",
    "\n",
    "As for the leakage at cycles 4202{4203, our original hypothesis was that this leakage was due to the act of writing (versus not writing) the target memory. These results suggest that the leakage originates from the *control logic* for the writes, rather than thewrites themselves. The countermeasure did not eliminate the secret-dependent write control logic; it merely altered (shifted) it.\n",
    "\n",
    "This attempt is included to illustrate that hiding leakage is not as easy as it looks! Before moving on, let's look at how many bits we can correctly guess on a single trace.\n",
    "\n",
    "First we reload the new bitfile:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "change_bitfile('attempt1')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we establish the decision thresholds using a known $k$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k = 0x0000ffffffffff000000000000ffff00aaaa0000cccc00001111000033330000\n",
    "traces = get_traces(30, k, 'part3_2', full=False, samples_per_segment=64)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The results above show that the leakage times at the end of the bit processing have shifted by one cycle, so we make a change to the `poi` array; then we establish the thresholds as usual:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "poi = [4201, -4202, -6, 7]\n",
    "\n",
    "sums = get_corrected_sums(traces, poi)\n",
    "\n",
    "poi_init_threshold = sums[16] - (sums[16] - np.average(sums[:16]))/2\n",
    "poi_reg_threshold = (np.average(sums[103:119]) - np.average(sums[56:103]))/2 + np.average(sums[56:103])\n",
    "\n",
    "print('Init threhold: %3.2f, regular threshold: %3.2f' % (poi_init_threshold, poi_reg_threshold))\n",
    "\n",
    "attempt1thresholds = [poi_init_threshold, poi_reg_threshold]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As a sanity check, we check that we correctly guess $k$ when multiple traces are averaged:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sums = get_corrected_sums(traces, poi)\n",
    "guess = poi_guess(sums, attempt1thresholds)\n",
    "print(\"DoM: %s\" % check_guess(guess, k)[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can finally see how many errors we make on single-trace attacks, on average:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "traces = get_traces(100, k, 'part3_3', randomize_k=True, full=False, samples_per_segment=64)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "wrong_bits = []\n",
    "for trace in traces:\n",
    "    sums = get_corrected_sums([trace], poi)\n",
    "    guess = poi_guess(sums, attempt1thresholds)\n",
    "    wrong_bits.append(check_guess(guess, trace.textin['k'])[1])\n",
    "\n",
    "print('Average wrong bits per trace: %f' % np.average(wrong_bits))\n",
    "print('Minimum wrong bits per trace: %f' % min(wrong_bits))\n",
    "print('Maximum wrong bits per trace: %f' % max(wrong_bits))\n",
    "\n",
    "attempt1_average_wrong_bits = np.average(wrong_bits)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This should be slightly lower than what you've seen with the original bitfile in part 2.\n",
    "\n",
    "This confirms that this countermeasure attempt has actually *increased* the side-channel leakage."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Attempt #2\n",
    "\n",
    "Next, we double down on our first approach to illustrate how misguided it really is: instead of doubling the target memory space, let's **quadruple** it!\n",
    "\n",
    "We set aside the cycle 6-7 leakage for now and take a second shot at hiding the leakage at cycles 4202-4203 by attempting to better uncoupling the memory write control logic from $k$.\n",
    "\n",
    "In attempt #1, each half of the intermediate result memories played a static role (one always held good intermediate results, the other always held unused intermediate results).\n",
    "\n",
    "Now, we quadruple the target memory space and alter the write destination logic so that the good intermediate results can go to any of the four memory sections, and ensure that the destination memory changes at every bit of $k$, regardless of its value.\n",
    "\n",
    "As before, we start by comparing the raw leakage:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "change_bitfile('attempt2')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k = 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000\n",
    "attempt2traces = get_traces(1, k, 'part3_4', full=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "attempt2trace = attempt2traces[0]\n",
    "attempt2avg_ones = np.zeros(samples)\n",
    "for start in cycles[1:128]:\n",
    "    attempt2avg_ones += attempt2trace.wave[start:start+samples]\n",
    "attempt2avg_ones /= 128\n",
    "\n",
    "attempt2avg_zeros = np.zeros(samples)\n",
    "for start in cycles[128:256]:\n",
    "    attempt2avg_zeros += attempt2trace.wave[start:start+samples]\n",
    "attempt2avg_zeros /= 128"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "diff = figure(width=2000)\n",
    "\n",
    "attempt2diff = attempt2avg_ones - attempt2avg_zeros\n",
    "\n",
    "compressed_attempt2diff = np.append(attempt2diff[0:15], attempt2diff[4195:])\n",
    "xrange = list(range(len(compressed_newdiff)))\n",
    "O = diff.line(xrange, compressed_odiff, line_color=\"black\")\n",
    "N1= diff.line(xrange, compressed_newdiff, line_color=\"orange\", line_width=2)\n",
    "N2= diff.line(xrange, compressed_attempt2diff, line_color=\"red\", line_width=3)\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='original 0/1 difference', renderers=[O]),\n",
    "    LegendItem(label='attempt #1 0/1 difference', renderers=[N1]),\n",
    "    LegendItem(label='attempt #2 0/1 difference', renderers=[N2]),\n",
    "])\n",
    "diff.add_layout(legend)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(diff)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = figure(width=2000)\n",
    "\n",
    "xrange = list(range(len(attempt2diff)))\n",
    "s.line(xrange, attempt2diff, line_color=\"red\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On the first plot, we found that the leakage previously seen is somewhat reduced but still present, and stretched out over more cycles at the end.\n",
    "\n",
    "However the second plot shows *new* strong leakage at cycles 1428-1429.\n",
    "\n",
    "If you have the required hardware, you can run the same analysis that we did for attempt #1 -- the only change required is `poi` as follows.\n",
    "\n",
    "No pre-recorded traces are provided for this step because the new points of interest at samples 1428-1429 are \"badly placed\" in that they would require too much storage for the traces, so you'll have to skip this part. However the results we've obtained so far show very clearly that this countermeasure is not effective. The average number of wrong bits obtained from actual traces is provided here, for comparison."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES == 'SIMULATED':\n",
    "    attempt2_average_wrong_bits = 36.37"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "poi = [4199, -4200, 4201, -4202, -6, 7, -1428, 1429]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED':\n",
    "    k = 0x0000ffffffffff000000000000ffff00aaaa0000cccc00001111000033330000\n",
    "    traces = get_traces(30, k, full=True)\n",
    "    sums = get_corrected_sums(traces, poi)\n",
    "\n",
    "    poi_init_threshold = sums[16] - (sums[16] - np.average(sums[:16]))/2\n",
    "    poi_reg_threshold = (np.average(sums[103:119]) - np.average(sums[56:103]))/2 + np.average(sums[56:103])\n",
    "    print('Init threhold: %3.2f, regular threshold: %3.2f' % (poi_init_threshold, poi_reg_threshold))\n",
    "    attempt2thresholds = [poi_init_threshold, poi_reg_threshold]\n",
    "\n",
    "    guess = poi_guess(sums, attempt2thresholds)\n",
    "    print(\"DoM: %s\" % check_guess(guess, k)[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED':\n",
    "    traces = get_traces(100, k, randomize_k=True, full=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED':\n",
    "    wrong_bits = []\n",
    "    for trace in traces:\n",
    "        sums = get_corrected_sums([trace], poi)\n",
    "        guess = poi_guess(sums, attempt2thresholds)\n",
    "        wrong_bits.append(check_guess(guess, trace.textin['k'])[1])\n",
    "\n",
    "    print('Average wrong bits per trace: %f' % np.average(wrong_bits))\n",
    "    print('Minimum wrong bits per trace: %f' % min(wrong_bits))\n",
    "    print('Maximum wrong bits per trace: %f' % max(wrong_bits))\n",
    "\n",
    "    attempt2_average_wrong_bits = np.average(wrong_bits)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should find that the average number of wrong bits per trace is close to that of attempt #1, and perhaps less.\n",
    "\n",
    "Clearly, this isn't working!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Attempt #3\n",
    "\n",
    "Instead of devising additional measures of increasing complexity to hide the leakage, we now take a completely different approach: adding \"noise\".\n",
    "\n",
    "The earlier improvements are abandoned; the $k$-dependent write logic and the target memories are returned to their original state.\n",
    "\n",
    "Instead, we add dummy logic which operates in tandem with the original leaky logic; the objective of this new logic is to add noise which hides the leakage.\n",
    "\n",
    "We do this by instantiating additional copies of the target memories. These copies are exercised with the same control logic as the real target memories, except that an LFSR is used to pseudo-randomly enable or disable the writes. The goal is for the noise memories to be active at the same time of the leakage, but in a way that does not depend on $k$. \n",
    "\n",
    "Experimentally, we find that adding a single \"noise\" memory for each of the 3 target memories does not help much, so we crank the noise up to 16 noise memories per target memory (48 in total).\n",
    "\n",
    "Note that only the `CW305_100t` target platform supports this attempt; the 35t FPGA does not have sufficient BRAM for it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "change_bitfile('attempt3')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Due to the LFSR, some additional setup is required for this bitfile:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED':\n",
    "    # Initialize the LFSR:\n",
    "    target.fpga_write(target.REG_LFSR_STATE, [101,22,35,43])\n",
    "    target.fpga_write(target.REG_LFSR_GO, [1]) \n",
    "\n",
    "    # enable all 16 noise memories:\n",
    "    target.fpga_write(target.REG_NOISE_ENABLE, [0xff, 0xff])\n",
    "\n",
    "    # Noise memories are controlled by the 16-bit REG_NOISE_ENABLE register; each bit enables one noise memory.\n",
    "    #target.fpga_write(target.REG_NOISE_ENABLE, [0x00, 0xff]) # enable half noise memories\n",
    "    #target.fpga_write(target.REG_NOISE_ENABLE, [0x00, 0x03]) # enable two noise memories"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k = 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000\n",
    "attempt3traces = get_traces(1, k, 'part3_5', full=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "attempt3trace = attempt3traces[0]\n",
    "attempt3avg_ones = np.zeros(samples)\n",
    "for start in cycles[1:128]:\n",
    "    attempt3avg_ones += attempt3trace.wave[start:start+samples]\n",
    "attempt3avg_ones /= 128\n",
    "\n",
    "attempt3avg_zeros = np.zeros(samples)\n",
    "for start in cycles[128:256]:\n",
    "    attempt3avg_zeros += attempt3trace.wave[start:start+samples]\n",
    "attempt3avg_zeros /= 128"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "diff = figure(width=2000)\n",
    "\n",
    "attempt3diff = attempt3avg_ones - attempt3avg_zeros\n",
    "compressed_attempt3diff = np.append(attempt3diff[0:15], attempt3diff[4195:])\n",
    "\n",
    "xrange = list(range(len(compressed_newdiff)))\n",
    "O = diff.line(xrange, compressed_odiff, line_color=\"black\")\n",
    "N1= diff.line(xrange, compressed_newdiff, line_color=\"orange\", line_width=2)\n",
    "N2= diff.line(xrange, compressed_attempt2diff, line_color=\"red\", line_width=3)\n",
    "N3= diff.line(xrange, compressed_attempt3diff, line_color=\"green\", line_width=6)\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='original 0/1 difference', renderers=[O]),\n",
    "    LegendItem(label='attempt #1 0/1 difference', renderers=[N1]),\n",
    "    LegendItem(label='attempt #2 0/1 difference', renderers=[N2]),\n",
    "    LegendItem(label='attempt #3 0/1 difference', renderers=[N3]),\n",
    "])\n",
    "diff.add_layout(legend)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(diff)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At first glance the results are not encouraging, until you realize that when we look at the average between ones and zeros, we are comparing the average power trace segment of two groups of 128 bits from the same trace.\n",
    "\n",
    "This averaging over 128 bits allows the new added noise to get averaged out quite well. Remember, real-world attacks do not allow this averaging.\n",
    "\n",
    "We hope that when we move to single-trace attacks, where each bit of k is treated individually, without the benefits of averaging, we'll see better results.\n",
    "\n",
    "First we update our POIs:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "poi = [4201, -4202, -6, 7]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we extract the thresholds:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k = 0x0000ffffffffff000000000000ffff00aaaa0000cccc00001111000033330000\n",
    "traces = get_traces(30, k, 'part3_6', full=False, samples_per_segment=64)\n",
    "sums = get_corrected_sums(traces, poi)\n",
    "poi_init_threshold = sums[16] - (sums[16] - np.average(sums[:16]))/2\n",
    "poi_reg_threshold = (np.average(sums[103:119]) - np.average(sums[56:103]))/2 + np.average(sums[56:103])\n",
    "print('Init threhold: %3.2f, regular threshold: %3.2f' % (poi_init_threshold, poi_reg_threshold))\n",
    "attempt3threshold = [poi_init_threshold, poi_reg_threshold]\n",
    "\n",
    "guess = poi_guess(sums, attempt3threshold)\n",
    "print(\"DoM: %s\" % check_guess(guess, k)[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we repeat the single-trace attack:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "traces = get_traces(100, k, 'part3_7', randomize_k=True, full=False, samples_per_segment=64)\n",
    "\n",
    "wrong_bits = []\n",
    "for trace in traces:\n",
    "    sums = get_corrected_sums([trace], poi)\n",
    "    guess = poi_guess(sums, attempt3threshold)\n",
    "    wrong_bits.append(check_guess(guess, trace.textin['k'])[1])\n",
    "\n",
    "print('Average wrong bits per trace: %f' % np.average(wrong_bits))\n",
    "print('Minimum wrong bits per trace: %f' % min(wrong_bits))\n",
    "print('Maximum wrong bits per trace: %f' % max(wrong_bits))\n",
    "\n",
    "attempt3_average_wrong_bits = np.average(wrong_bits)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, you should see a big jump (to around 70) in the average number of wrong bits per trace. This countermeasure works!\n",
    "\n",
    "Summarizing the results so far:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Average number of wrong bit guesses for a single trace attack:')\n",
    "print('Attempt 1: %5.1f' % attempt1_average_wrong_bits)\n",
    "print('Attempt 2: %5.1f' % attempt2_average_wrong_bits)\n",
    "print('Attempt 3: %5.1f' % attempt3_average_wrong_bits)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you re-run this section with fewer noise memories enabled, you should see a corresponding decrease in the number of wrong bit guesses (don't forget to re-establish the thresholds).\n",
    "\n",
    "With a single noise memory enabled, there should not be much difference from the results obtained from the original bitfile."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# The Hidden Number Problem\n",
    "\n",
    "To finish, let's see how effective the attempt #3 countermeasure may be at increasing the number of traces required for recovering $k$ in a real-world attack.\n",
    "\n",
    "We'll run this with the attempt #3 bitfile, but you can run it on the other attempts if you wish (some adjustments to the Husky segmented capture are required if you use a bitfile with different POIs; however you can use the (slightly slower) CWPRO segmented capture routine for Husky without any modifications).\n",
    "\n",
    "First, make sure that the previous section was run with all noise memories enabled (or not, if you want to see different results!).\n",
    "\n",
    "We'll acquire segmented traces, like we did in part 2.\n",
    "\n",
    "In the interest of storage constraints, these traces are not saved, so you'll need the required hardware to run this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "poi = [-6, 7, 4201, -4202] # pois need to be in this particular order for the Husky timed segmented capture to work\n",
    "trace_segments = get_trace_segments(N=5000, poi=poi, randomize_k=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "consecutives(trace_segments=trace_segments, poi=poi, distance_threshold=0.80, thresholds=attempt3threshold)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some adjustment on `distance_threshold` may be required, but you should find substantially fewer good traces compared to the original target results from part 2."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Conclusion\n",
    "\n",
    "In this part we tried different countermeasures: some which don't work very well, and one that does. Hopefully this gave you some insight on the challenges of addressing side-channel leakage from the implementer's side.\n",
    "\n",
    "From the attacker's side, this also showed how the attack developed in part 2 can still be effective when countermeasures are added.\n",
    "\n",
    "In part 4 we'll look at a final countermeasure which will reveal that our target has additional side-channel leakage which we haven't leveraged yet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
